

/*************************************************************************
 *  Compilation:  javac Draw.java
 *  Execution:    java Draw
 *
 *  Drawing library. This class provides a basic capability for creating
 *  drawings with your programs. It uses a simple graphics model that
 *  allows you to create drawings consisting of points, lines, and curves
 *  in a window on your computer and to save the drawings to a file.
 *  This is the object-oriented version of standard draw; it supports
 *  multiple indepedent drawing windows.
 *
 *  Todo
 *  ----
 *    -  Add support for gradient fill, etc.
 *
 *  Remarks
 *  -------
 *    -  don't use AffineTransform for rescaling since it inverts
 *       images and strings
 *    -  careful using setFont in inner loop within an animation -
 *       it can cause flicker
 *
 *************************************************************************/

import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import javax.imageio.ImageIO;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

/**
 *  <i>Draw</i>. This class provides a basic capability for
 *  creating drawings with your programs. It uses a simple graphics model that
 *  allows you to create drawings consisting of points, lines, and curves
 *  in a window on your computer and to save the drawings to a file.
 *  This is the object-oriented version of standard draw; it supports
 *  multiple indepedent drawing windows.
 *  <p>
 *  For additional documentation, see <a href="http://introcs.cs.princeton.edu/31datatype">Section 3.1</a> of
 *  <i>Introduction to Programming in Java: An Interdisciplinary Approach</i> by Robert Sedgewick and Kevin Wayne.
 */
public final class Draw  {

	// pre-defined colors
	public static final Color BLACK      = Color.BLACK;
	public static final Color BLUE       = Color.BLUE;
	public static final Color CYAN       = Color.CYAN;
	public static final Color DARK_GRAY  = Color.DARK_GRAY;
	public static final Color GRAY       = Color.GRAY;
	public static final Color GREEN      = Color.GREEN;
	public static final Color LIGHT_GRAY = Color.LIGHT_GRAY;
	public static final Color MAGENTA    = Color.MAGENTA;
	public static final Color ORANGE     = Color.ORANGE;
	public static final Color PINK       = Color.PINK;
	public static final Color RED        = Color.RED;
	public static final Color WHITE      = Color.WHITE;
	public static final Color YELLOW     = Color.YELLOW;

	// default colors
	private static final Color DEFAULT_PEN_COLOR   = BLACK;
	private static final Color DEFAULT_CLEAR_COLOR = LIGHT_GRAY;

	// current pen color
	private Color penColor;

	// default canvas size is SIZE-by-SIZE
	private static final int DEFAULT_SIZE = 512;
	private int width  = DEFAULT_SIZE;
	private int height = DEFAULT_SIZE;

	// default pen radius
	private static final double DEFAULT_PEN_RADIUS = 0.002;

	// current pen radius
	private double penRadius;

	// show we draw immediately or wait until next show?
	private boolean defer = false;

	// boundary of drawing canvas, 5% border
	private static final double BORDER = 0.05;
	private static final double DEFAULT_XMIN = -10.0;
	private static final double DEFAULT_XMAX = 10.0;
	private static final double DEFAULT_YMIN = -10.0;
	private static final double DEFAULT_YMAX = 10.0;
	private double xmin, ymin, xmax, ymax;

	// name of window
	private String name = "Draw";

	// default font
	private static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 16);

	// current font
	private Font font;

	// double buffered graphics
	private BufferedImage offscreenImage, onscreenImage;
	private Graphics2D offscreen, onscreen;

	// the frame for drawing to the screen
	private JFrame frame = new JFrame();

	/**
	 * Create an empty drawing object with the given name.
	 *
	 * @param name the title of the drawing window.
	 */
	public Draw(String name) {
		this.name = name;
		init();
	}

	/**
	 * Create an empty drawing object.
	 */
	public Draw() {
		init();
	}

	private void init() {
		if (frame != null) frame.setVisible(false);
		frame = new JFrame();
		offscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		onscreenImage  = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		offscreen = offscreenImage.createGraphics();
		onscreen  = onscreenImage.createGraphics();
		setXscale();
		setYscale();
		offscreen.setColor(DEFAULT_CLEAR_COLOR);
		offscreen.fillRect(0, 0, width, height);
		setPenColor();
		setPenRadius();
		setFont();
		clear();

		// add anti-aliasing
		RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		offscreen.addRenderingHints(hints);

		// frame stuff
		ImageIcon icon = new ImageIcon(onscreenImage);
		JLabel draw = new JLabel(icon);


		frame.setContentPane(draw);
		// JLabel cannot get keyboard focus
		frame.setResizable(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);            // closes all windows
		//frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);      // closes only current window
		frame.setTitle(name);
		frame.setJMenuBar(createMenuBar());
		frame.pack();
		frame.requestFocusInWindow();
		
		frame.setVisible(true);
	}


	/**
	 * Set the upper-left hand corner of the drawing window to be (x, y), where (0, 0) is upper left.
	 *
	 * @param x the number of pixels from the left
	 * @param y the number of pixels from the top
	 * @throws a RunTimeException if the width or height is 0 or negative
	 */
	public void setLocationOnScreen(int x, int y) {
		frame.setLocation(x, y);
	}



	/**
	 * Set the window size to w-by-h pixels.
	 *
	 * @param w the width as a number of pixels
	 * @param h the height as a number of pixels
	 * @throws a RunTimeException if the width or height is 0 or negative
	 */
	public void setCanvasSize(int w, int h) {
		if (w < 1 || h < 1) throw new RuntimeException("width and height must be positive");
		width = w;
		height = h;
		init();
	}


	// create the menu bar (changed to private)
	private JMenuBar createMenuBar() {
		JMenuBar menuBar = new JMenuBar();
		JMenu menu = new JMenu("File");
		menuBar.add(menu);

		JMenuItem menuItem1 = new JMenuItem(" Save...   ");
		menuItem1.addMouseListener(new MouseListener(){

			@Override
			public void mouseClicked(MouseEvent e) {
				// TODO Auto-generated method stub

			}

			@Override
			public void mouseEntered(MouseEvent e) {
				// TODO Auto-generated method stub

			}

			@Override
			public void mouseExited(MouseEvent e) {
				// TODO Auto-generated method stub

			}

			@Override
			public void mousePressed(MouseEvent e) {
				// TODO Auto-generated method stub
				FileDialog chooser = new FileDialog(frame, "Use a .png or .jpg extension", FileDialog.SAVE);
				chooser.setVisible(true);
				String filename = chooser.getFile();
				if (filename != null) {
					save(chooser.getDirectory() + File.separator + chooser.getFile());
				}
			}

			@Override
			public void mouseReleased(MouseEvent e) {
				// TODO Auto-generated method stub

			}});
		menu.add(menuItem1);
		JMenuItem menuItem2 = new JMenuItem("Exit Program");	
		menuItem2.addMouseListener(new MouseListener(){

			@Override
			public void mouseClicked(MouseEvent arg0) {
				// TODO Auto-generated method stub
				//System.out.println("CLICKED");
				System.exit(0);
			}

			@Override
			public void mouseEntered(MouseEvent arg0) {
				// TODO Auto-generated method stub

			}

			@Override
			public void mouseExited(MouseEvent arg0) {
				// TODO Auto-generated method stub

			}

			@Override
			public void mousePressed(MouseEvent arg0) {
				// TODO Auto-generated method stub
				//System.out.println("Pressed");
				System.exit(0);
			}

			@Override
			public void mouseReleased(MouseEvent arg0) {
				// TODO Auto-generated method stub

			}

		});
		menu.add(menuItem2);
		return menuBar;  

	}


	/*************************************************************************
	 *  User and screen coordinate systems
	 *************************************************************************/

	/**
	 * Set the x-scale to be the default (between 0.0 and 1.0).
	 */
	public void setXscale() { setXscale(DEFAULT_XMIN, DEFAULT_XMAX); }

	/**
	 * Set the y-scale to be the default (between 0.0 and 1.0).
	 */
	public void setYscale() { setYscale(DEFAULT_YMIN, DEFAULT_YMAX); }

	/**
	 * Set the x-scale (a 10% border is added to the values)
	 * @param min the minimum value of the x-scale
	 * @param max the maximum value of the x-scale
	 */
	public void setXscale(double min, double max) {
		double size = max - min;
		xmin = min - BORDER * size;
		xmax = max + BORDER * size;
		System.out.println("X Scale set to " + xmin + " to "+ xmax);
	}

	/**
	 * Set the y-scale (a 10% border is added to the values).
	 * @param min the minimum value of the y-scale
	 * @param max the maximum value of the y-scale
	 */
	public void setYscale(double min, double max) {
		double size = max - min;
		ymin = min - BORDER * size;
		ymax = max + BORDER * size;
		System.out.println("Y Scale set to " + ymin + " to "+ ymax);
	}
	public void drawString(String input){
		Font font = new Font("Serif", Font.PLAIN, 16);
        offscreen.setFont(font);
		offscreen.drawString(input,15, 15);
	}

	// helper functions that scale from user coordinates to screen coordinates and back
	private double  scaleX(double x) { return width  * (x - xmin) / (xmax - xmin); }
	private double  scaleY(double y) { return height * (ymax - y) / (ymax - ymin); }
	private double factorX(double w) { return w * width  / Math.abs(xmax - xmin);  }
	private double factorY(double h) { return h * height / Math.abs(ymax - ymin);  }
	private double   userX(double x) { return xmin + x * (xmax - xmin) / width;    }
	private double   userY(double y) { return ymax - y * (ymax - ymin) / height;   }


	/**
	 * Clear the screen to the default color (white).
	 */
	public void clear() { clear(DEFAULT_CLEAR_COLOR); }
	/**
	 * Clear the screen to the given color.
	 * @param color the Color to make the background
	 */
	public void clear(Color color) {
		offscreen.setColor(color);
		offscreen.fillRect(0, 0, width, height);
		offscreen.setColor(penColor);
		draw();
	}

	/**
	 * Get the current pen radius.
	 */
	public double getPenRadius() { return penRadius; }

	/**
	 * Set the pen size to the default (.002).
	 */
	public void setPenRadius() { setPenRadius(DEFAULT_PEN_RADIUS); }

	/**
	 * Set the radius of the pen to the given size.
	 * @param r the radius of the pen
	 * @throws RuntimeException if r is negative
	 */
	public void setPenRadius(double r) {
		if (r < 0) throw new RuntimeException("pen radius must be positive");
		penRadius = r * DEFAULT_SIZE;
		BasicStroke stroke = new BasicStroke((float) penRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
		// BasicStroke stroke = new BasicStroke((float) penRadius);
		offscreen.setStroke(stroke);
	}

	/**
	 * Get the current pen color.
	 */
	public Color getPenColor() { return penColor; }

	/**
	 * Set the pen color to the default color (black).
	 */
	public void setPenColor() { setPenColor(DEFAULT_PEN_COLOR); }

	/**
	 * Set the pen color to the given color.
	 * @param color the Color to make the pen
	 */
	public void setPenColor(Color color) {
		penColor = color;
		offscreen.setColor(penColor);
	}

	/**
	 * Get the current font.
	 */
	public Font getFont() { return font; }

	/**
	 * Set the font to the default font (sans serif, 16 point).
	 */
	public void setFont() { setFont(DEFAULT_FONT); }

	/**
	 * Set the font to the given value.
	 * @param f the font to make text
	 */
	public void setFont(Font f) { font = f; }


	/*************************************************************************
	 *  Drawing geometric shapes.
	 *************************************************************************/

	/**
	 * Draw a line from (x0, y0) to (x1, y1).
	 * @param x0 the x-coordinate of the starting point
	 * @param y0 the y-coordinate of the starting point
	 * @param x1 the x-coordinate of the destination point
	 * @param y1 the y-coordinate of the destination point
	 */
	public void line(double x0, double y0, double x1, double y1) {
		offscreen.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1)));
		draw();
	}
	
	public void line(Color color,double R, double x0, double y0, double x1, double y1) {
		Color tempColor = this.getPenColor();
		double tempRad = this.getPenRadius();
		this.setPenColor(color);
		this.setPenRadius(R);
		offscreen.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1)));
		this.setPenColor(tempColor);
		this.setPenRadius(tempRad);
		draw();
		
	}
	/**
	 * Draw one pixel at (x, y).
	 * @param x the x-coordinate of the pixel
	 * @param y the y-coordinate of the pixel
	 */
	private void pixel(double x, double y) {
		offscreen.fillRect((int) Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1);
	}

	/**
	 * Draw a point at (x, y).
	 * @param x the x-coordinate of the point
	 * @param y the y-coordinate of the point
	 */
	public void point(double x, double y) {
		double xs = scaleX(x);
		double ys = scaleY(y);
		double r = penRadius;
		// double ws = factorX(2*r);
		// double hs = factorY(2*r);
		// if (ws <= 1 && hs <= 1) pixel(x, y);
		if (r <= 1) pixel(x, y);
		else offscreen.fill(new Ellipse2D.Double(xs - r/2, ys - r/2, r, r));
		draw();
	}

	/**
	 * Draw a circle of radius r, centered on (x, y).
	 * @param x the x-coordinate of the center of the circle
	 * @param y the y-coordinate of the center of the circle
	 * @param r the radius of the circle
	 * @throws RuntimeException if the radius of the circle is negative
	 */
	public void circle(double x, double y, double r) {
		if (r < 0) throw new RuntimeException("circle radius can't be negative");
		double xs = scaleX(x);
		double ys = scaleY(y);
		double ws = factorX(2*r);
		double hs = factorY(2*r);
		if (ws <= 1 && hs <= 1) pixel(x, y);
		else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
		draw();
	}

	/**
	 * Draw filled circle of radius r, centered on (x, y).
	 * @param x the x-coordinate of the center of the circle
	 * @param y the y-coordinate of the center of the circle
	 * @param r the radius of the circle
	 * @throws RuntimeException if the radius of the circle is negative
	 */
	public void filledCircle(double x, double y, double r) {
		if (r < 0) throw new RuntimeException("circle radius can't be negative");
		double xs = scaleX(x);
		double ys = scaleY(y);
		double ws = factorX(2*r);
		double hs = factorY(2*r);
		if (ws <= 1 && hs <= 1) pixel(x, y);
		else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
		draw();
	}


	/**
	 * Draw an ellipse with given semimajor and semiminor axes, centered on (x, y).
	 * @param x the x-coordinate of the center of the ellipse
	 * @param y the y-coordinate of the center of the ellipse
	 * @param semiMajorAxis is the semimajor axis of the ellipse
	 * @param semiMinorAxis is the semiminor axis of the ellipse
	 * @throws RuntimeException if either of the axes are negative
	 */
	public void ellipse(double x, double y, double semiMajorAxis, double semiMinorAxis) {
		if (semiMajorAxis < 0) throw new RuntimeException("ellipse semimajor axis can't be negative");
		if (semiMinorAxis < 0) throw new RuntimeException("ellipse semiminor axis can't be negative");
		double xs = scaleX(x);
		double ys = scaleY(y);
		double ws = factorX(2*semiMajorAxis);
		double hs = factorY(2*semiMinorAxis);
		if (ws <= 1 && hs <= 1) pixel(x, y);
		else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
		draw();
	}

	/**
	 * Draw an ellipse with given semimajor and semiminor axes, centered on (x, y).
	 * @param x the x-coordinate of the center of the ellipse
	 * @param y the y-coordinate of the center of the ellipse
	 * @param semiMajorAxis is the semimajor axis of the ellipse
	 * @param semiMinorAxis is the semiminor axis of the ellipse
	 * @throws RuntimeException if either of the axes are negative
	 */
	public void filledEllipse(double x, double y, double semiMajorAxis, double semiMinorAxis) {
		if (semiMajorAxis < 0) throw new RuntimeException("ellipse semimajor axis can't be negative");
		if (semiMinorAxis < 0) throw new RuntimeException("ellipse semiminor axis can't be negative");
		double xs = scaleX(x);
		double ys = scaleY(y);
		double ws = factorX(2*semiMajorAxis);
		double hs = factorY(2*semiMinorAxis);
		if (ws <= 1 && hs <= 1) pixel(x, y);
		else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
		draw();
	}

	/**
	 * Draw an arc of radius r, centered on (x, y), from angle1 to angle2 (in degrees).
	 * @param x the x-coordinate of the center of the circle
	 * @param y the y-coordinate of the center of the circle
	 * @param r the radius of the circle
	 * @param angle1 the starting angle. 0 would mean an arc beginning at 3 o'clock.
	 * @param angle2 the angle at the end of the arc. For example, if
	 *        you want a 90 degree arc, then angle2 should be angle1 + 90.
	 * @throws RuntimeException if the radius of the circle is negative
	 */
	public void arc(double x, double y, double r, double angle1, double angle2) {
		if (r < 0) throw new RuntimeException("arc radius can't be negative");
		while (angle2 < angle1) angle2 += 360;
		double xs = scaleX(x);
		double ys = scaleY(y);
		double ws = factorX(2*r);
		double hs = factorY(2*r);
		if (ws <= 1 && hs <= 1) pixel(x, y);
		else offscreen.draw(new Arc2D.Double(xs - ws/2, ys - hs/2, ws, hs, angle1, angle2 - angle1, Arc2D.OPEN));
		draw();
	}

	/**
	 * Draw a square of side length 2r, centered on (x, y).
	 * @param x the x-coordinate of the center of the square
	 * @param y the y-coordinate of the center of the square
	 * @param r radius is half the length of any side of the square
	 * @throws RuntimeException if r is negative
	 */
	public void square(double x, double y, double r) {
		if (r < 0) throw new RuntimeException("square side length can't be negative");
		double xs = scaleX(x);
		double ys = scaleY(y);
		double ws = factorX(2*r);
		double hs = factorY(2*r);
		if (ws <= 1 && hs <= 1) pixel(x, y);
		else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
		draw();
	}

	/**
	 * Draw a filled square of side length 2r, centered on (x, y).
	 * @param x the x-coordinate of the center of the square
	 * @param y the y-coordinate of the center of the square
	 * @param r radius is half the length of any side of the square
	 * @throws RuntimeException if r is negative
	 */
	public void filledSquare(double x, double y, double r) {
		if (r < 0) throw new RuntimeException("square side length can't be negative");
		double xs = scaleX(x);
		double ys = scaleY(y);
		double ws = factorX(2*r);
		double hs = factorY(2*r);
		if (ws <= 1 && hs <= 1) pixel(x, y);
		else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
		draw();
	}


	/**
	 * Draw a rectangle of given half width and half height, centered on (x, y).
	 * @param x the x-coordinate of the center of the rectangle
	 * @param y the y-coordinate of the center of the rectangle
	 * @param halfWidth is half the width of the rectangle
	 * @param halfHeight is half the height of the rectangle
	 * @throws RuntimeException if halfWidth or halfHeight is negative
	 */
	public void rectangle(double x, double y, double halfWidth, double halfHeight) {
		if (halfWidth  < 0) throw new RuntimeException("half width can't be negative");
		if (halfHeight < 0) throw new RuntimeException("half height can't be negative");
		double xs = scaleX(x);
		double ys = scaleY(y);
		double ws = factorX(2*halfWidth);
		double hs = factorY(2*halfHeight);
		if (ws <= 1 && hs <= 1) pixel(x, y);
		else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
		draw();
	}

	/**
	 * Draw a filled rectangle of given half width and half height, centered on (x, y).
	 * @param x the x-coordinate of the center of the rectangle
	 * @param y the y-coordinate of the center of the rectangle
	 * @param halfWidth is half the width of the rectangle
	 * @param halfHeight is half the height of the rectangle
	 * @throws RuntimeException if halfWidth or halfHeight is negative
	 */
	public void filledRectangle(double x, double y, double halfWidth, double halfHeight) {
		if (halfWidth  < 0) throw new RuntimeException("half width can't be negative");
		if (halfHeight < 0) throw new RuntimeException("half height can't be negative");
		double xs = scaleX(x);
		double ys = scaleY(y);
		double ws = factorX(2*halfWidth);
		double hs = factorY(2*halfHeight);
		if (ws <= 1 && hs <= 1) pixel(x, y);
		else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
		draw();
	}

	/**
	 * Draw a polygon with the given (x[i], y[i]) coordinates.
	 * @param x an array of all the x-coordindates of the polygon
	 * @param y an array of all the y-coordindates of the polygon
	 */
	public void polygon(double[] x, double[] y) {
		int N = x.length;
		GeneralPath path = new GeneralPath();
		path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0]));
		for (int i = 0; i < N; i++)
			path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i]));
		path.closePath();
		offscreen.draw(path);
		draw();
	}

	/**
	 * Draw a filled polygon with the given (x[i], y[i]) coordinates.
	 * @param x an array of all the x-coordindates of the polygon
	 * @param y an array of all the y-coordindates of the polygon
	 */
	public void filledPolygon(double[] x, double[] y) {
		int N = x.length;
		GeneralPath path = new GeneralPath();
		path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0]));
		for (int i = 0; i < N; i++)
			path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i]));
		path.closePath();
		offscreen.fill(path);
		draw();
	}






	/**
	 * Display on screen, pause for t milliseconds, and turn on
	 * <em>animation mode</em>: subsequent calls to
	 * drawing methods such as <tt>line()</tt>, <tt>circle()</tt>, and <tt>square()</tt>
	 * will not be displayed on screen until the next call to <tt>show()</tt>.
	 * This is useful for producing animations (clear the screen, draw a bunch of shapes,
	 * display on screen for a fixed amount of time, and repeat). It also speeds up
	 * drawing a huge number of shapes (call <tt>show(0)</tt> to defer drawing
	 * on screen, draw the shapes, and call <tt>show(0)</tt> to display them all
	 * on screen at once).
	 * @param t number of milliseconds
	 */
	public void show(int t) {
		defer = false;
		draw();
		try { Thread.currentThread().sleep(t); }
		catch (InterruptedException e) { System.out.println("Error sleeping"); }
		defer = true;
	}


	/**
	 * Display on-screen and turn off animation mode:
	 * subsequent calls to
	 * drawing methods such as <tt>line()</tt>, <tt>circle()</tt>, and <tt>square()</tt>
	 * will be displayed on screen when called. This is the default.
	 */
	public void show() {
		defer = false;
		draw();
	}

	// draw onscreen if defer is false
	private void draw() {
		if (defer) return;
		onscreen.drawImage(offscreenImage, 0, 0, null);

		frame.repaint();
	}


	/*************************************************************************
	 *  Save drawing to a file.
	 *************************************************************************/

	/**
	 * Save to file - suffix must be png, jpg, or gif.
	 * @param filename the name of the file with one of the required suffixes
	 */
	public void save(String filename) {
		File file = new File(filename);
		String suffix = filename.substring(filename.lastIndexOf('.') + 1);

		// png files
		if (suffix.toLowerCase().equals("png")) {
			try { ImageIO.write(offscreenImage, suffix, file); }
			catch (IOException e) { e.printStackTrace(); }
		}

		// need to change from ARGB to RGB for jpeg
		// reference: http://archives.java.sun.com/cgi-bin/wa?A2=ind0404&L=java2d-interest&D=0&P=2727
		else if (suffix.toLowerCase().equals("jpg")) {
			WritableRaster raster = offscreenImage.getRaster();
			WritableRaster newRaster;
			newRaster = raster.createWritableChild(0, 0, width, height, 0, 0, new int[] {0, 1, 2});
			DirectColorModel cm = (DirectColorModel) offscreenImage.getColorModel();
			DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(),
					cm.getRedMask(),
					cm.getGreenMask(),
					cm.getBlueMask());
			BufferedImage rgbBuffer = new BufferedImage(newCM, newRaster, false,  null);
			try { 
				ImageIO.write(rgbBuffer, suffix, file);
				JOptionPane.showMessageDialog(frame,
						"Map Saved!", 
						"Complete", 1);
			}
			catch (IOException e) { e.printStackTrace(); }
		}

		else {
			System.out.println("Invalid image file type: " + suffix);
		}
	}


	/**
	 * This method cannot be called directly.
	 */
	public void actionPerformed(ActionEvent e) {
		FileDialog chooser = new FileDialog(frame, "Use a .png or .jpg extension", FileDialog.SAVE);
		chooser.setVisible(true);
		String filename = chooser.getFile();
		if (filename != null) {
			save(chooser.getDirectory() + File.separator + chooser.getFile());
		}
	}





}

